#include <linux/if.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>	/*for in_get_dev*/
#include <net/addrconf.h>		/*for in6_get_dev*/
#include <linux/if_arp.h>
#include "socs/comm/config.h"
#include "socs/comm/types.h"
#include "socs/comm/socs.h"
#include "socs/mod/addr_opt.h"
#include "socs/mod/localnode.h"
#include "socs/mod/neigh.h"
#include "socs/mod/hello.h"


/*the inner API*/
static int locallink_add(const char *name, void *address, unsigned long sapcnt,
						 int is_localnode, struct localnode *lcn)
{
	int i, j;
	int index = lcn->lcl_num;
	unsigned int tmDead;
	int err;
	int cmp_ret = 0;

	//struct locallink *lcl=NULL;

	if (!address) {
		log_err(ERR_NULL_PTR, "NULL is not a valid lcl->address.");
		return -1;
	}

	node_for_each_link(i, lcn) {
		cmp_ret = addr_cmp(lcn->family, &lcn->lcls[i].address, address) ;
		log_debug("[locallink_add]addr_cmp =%d", cmp_ret);

		if (cmp_ret < 0) {
			index = i;
			break;
		} else if ( cmp_ret == 0) {
			return -1;
		}
	}

	for (j = lcn->lcl_num; j > index; j--) {
		del_timer(&lcn->lcls[j - 1].timer);
		memcpy(&lcn->lcls[j], &lcn->lcls[j - 1], SIZEOFLCL);
		setup_timer(&lcn->lcls[j].timer, locallink_expire,
					(unsigned long)&lcn->lcls[j]);

		if (!is_localnode) {
			socs_config_get(ARGP_TMDEAD, &tmDead);

			if ((err = mod_timer(&lcn->lcls[j].timer,
								 jiffies + msecs_to_jiffies(tmDead)))) {
				log_err(ERR_TIMER, "Error in mod_timer in locallink,err=%d", err);
				return -1;
			}
		}

		log_debug("[locallink_add]lcn->lcls[%d].timer expires is %lu,is_localnode=%d",
				  j, lcn->lcls[j].timer.expires, is_localnode);
	}

	log_debug("locallink_add index=%d", index);
	memset(&lcn->lcls[index], 0, SIZEOFLCL);

	if (lcn->family == IPV4) {
		memcpy(&lcn->lcls[index].address.ipv4, address, sizeof(struct in_addr));
	} else {
		memcpy(&lcn->lcls[index].address.ipv6, address, sizeof(struct in6_addr));
	}

	//lcl=(struct locallink *)kmalloc(SIZEOFLCL,GFP_ATOMIC);
	//memset(lcl,0,SIZEOFLCL);
	//memcpy(lcl,&lcn->lcls[index],SIZEOFLCL);
	//setup_timer(&lcn->lcls[index].timer,locallink_expire,(unsigned long)lcl);
	setup_timer(&lcn->lcls[index].timer, locallink_expire,
				(unsigned long)(&lcn->lcls[index]));

	if (!is_localnode) {
		//setup_timer(&lcn->lcls[index].timer,locallink_expire,(unsigned long)(&lcn->lcls[index]));
		socs_config_get(ARGP_TMDEAD, &tmDead);

		if ((err = mod_timer(&lcn->lcls[index].timer,
							 jiffies + msecs_to_jiffies(tmDead)))) {
			log_err(ERR_TIMER, "Error in mod_timer in locallink,err=%d", err);
			return -1;
		}
	}

	log_debug("[locallink_add]lcn->lcls[%d].timer expires is %lu,is_localnode=%d",
			  index, lcn->lcls[index].timer.expires, is_localnode);
	memcpy(lcn->lcls[index].name, name, IFNAMSIZ);
	lcn->lcls[index].sapcnt = sapcnt;
	atomic_set(&lcn->lcls[index].totalrq, 0);
	lcn->totalsapcnt += sapcnt;
	return 0;
}
int localnode4_get(struct net_device *dev , struct localnode *lcn)
{
	struct in_device *ind = NULL;
	struct in_ifaddr *ina = NULL;
	int err = -1;

	if (!netif_running(dev) || !dev->ethtool_ops->get_link(dev)) {
		log_debug("[localnode4_get]event change %s is not running", dev->name);
		return err;
	}

	ind = in_dev_get(dev);

	if (ind) {
		log_debug("localnode4_get dev->name:%s", dev->name);

		for (ina = ind->ifa_list; ina != NULL; ina = ina->ifa_next) {
			log_debug("%s\t%d", dev->name, dev->ifindex);
			log_debug("inet addr:%pI4", &ina->ifa_address);

			if (dev->ifindex == LOOPBACK_IFINDEX) {
				continue;
			}

			err = locallink_add(dev->name, &ina->ifa_address, 0, 1, lcn);

			if (err < 0) {
				log_err(ERR_ADD_FAILED, "%s  failed,err=%d", __func__, err);
				//return err;//modify be Leo.Huang 2015-03-04
				continue;
			}

			lcn->lcl_num++;
		}

		in_dev_put(ind);
	} else {
		log_debug("localnode4_get ind = NULL");
	}

	return 0;
}
int localnode6_get(struct net_device *dev , struct localnode *lcn)
{
	struct inet6_dev *in6d = NULL;
	struct inet6_ifaddr *in6a = NULL;
	int err = -1;

	if (!netif_running(dev) || !dev->ethtool_ops->get_link(dev)) {
		log_debug("[localnode6_get]event change %s is not running", dev->name);
		return err;
	}

	in6d = in6_dev_get(dev);

	if (in6d) {
		list_for_each_entry(in6a, &in6d->addr_list, if_list) {
			if (dev->ifindex == LOOPBACK_IFINDEX) {
				continue;
			}

			log_debug("%s\t%d\t", dev->name, dev->ifindex);
			log_debug("inet6 addr:%pI6c\n", &in6a->addr);

			if (ipv6_addr_type(&in6a->addr)&IPV6_ADDR_LINKLOCAL) {
				continue;
			}

			err = locallink_add(dev->name, &in6a->addr, 0, 1, lcn);

			if (err < 0) {
				log_err(ERR_ADD_FAILED, "%s failed,err=%d", __func__, err);
				//return err;//modify be Leo.Huang 2015-03-06
				continue;
			}

			lcn->lcl_num++;
		}
		in6_dev_put(in6d);
	} else {
		log_debug("localnode6_get in6d = NULL");
	}

	return 0;
}
/**********************************API for locallink*****************************************/
int locallink_remove(struct localnode *lcn, void *address,
					 int is_localnode) /*only call by node from neigh*/
{
	int i;
	//struct locallink * lcl=NULL;
	int err = 0;
	unsigned int tmDead;
	int index = -1;

	if (lcn->family == IPV4) {
		log_debug("locallink_remove address %pI4c", address);
	} else {
		log_debug("locallink_remove address %pI6c", address);
	}

	if (!address) {
		log_err(ERR_NULL_PTR, "NULL is not a valid address.");
		return -1;
	}

	node_for_each_link(i, lcn) {
		if (!addr_cmp(lcn->family, &lcn->lcls[i].address, address)) {
			del_timer(&lcn->lcls[i].timer);
			lcn->totalsapcnt -= lcn->lcls[i].sapcnt;
			index = i;
			break;
		}
	}

	if (index >= 0) {
		log_debug("locallink_remove index=%d,num=%d", index, lcn->lcl_num);
		lcn->lcl_num--;

		for (i = index; i < lcn->lcl_num; i++) {
			del_timer(&lcn->lcls[i + 1].timer);
			memcpy(&lcn->lcls[i], &lcn->lcls[i + 1], SIZEOFLCL);
			//lcl=(struct locallink *)kmalloc(SIZEOFLCL,GFP_ATOMIC);
			//memset(lcl,0,SIZEOFLCL);
			//memcpy(lcl,&lcn->lcls[i],SIZEOFLCL);
			setup_timer(&lcn->lcls[i].timer, locallink_expire,
						(unsigned long)&lcn->lcls[i]);

			if (!is_localnode) {
				socs_config_get(ARGP_TMDEAD, &tmDead);

				if ((err = mod_timer(&lcn->lcls[i].timer,
									 jiffies + msecs_to_jiffies(tmDead)))) {
					log_err(ERR_TIMER, "Error in mod_timer in locallink,err=%d", err);
					return -1;
				}
			}
		}

		localnode_show(lcn);
		return 0;
	}

	log_debug("locallink remove the locallink is not belong this localnode\n");
	return -1;
}
void locallink_remove_all(struct localnode *lcn)
{
	int i;
	node_for_each_link(i, lcn) {
		if (lcn->family == IPV4) {
			log_debug("locallink_remove_all name %s,address:%pI4", lcn->lcls[i].name,
					  &lcn->lcls[i].address);
		} else {
			log_debug("localnode_remove_all name %s,address:%pI6c", lcn->lcls[i].name,
					  &lcn->lcls[i].address);
		}

		del_timer(&lcn->lcls[i].timer);
		lcn->totalsapcnt -= lcn->lcls[i].sapcnt;
	}
	lcn->lcl_num = 0;
}
struct locallink *locallink_find_best(struct localnode *lcn)
{
	struct locallink *minsapcnt = &lcn->lcls[0];
	struct locallink *min_maxaddr = NULL;
	int i;
	int num_minsapcnt = 0;

	if (lcn == NULL) {
		return NULL;
	}

	node_for_each_link(i, lcn) {
		if (lcn->lcls[i].sapcnt < minsapcnt->sapcnt) {
			minsapcnt = &lcn->lcls[i];
		}
	}
	min_maxaddr = minsapcnt;
	node_for_each_link(i, lcn) {
		if (lcn->lcls[i].sapcnt == minsapcnt->sapcnt) {
			if (++num_minsapcnt > 1
				&& addr_cmp(lcn->family, &lcn->lcls[i].address, &min_maxaddr->address) > 0) {
				min_maxaddr = &lcn->lcls[i];
			}
		}
	}
	return min_maxaddr;
}
int locallink_decsapcnt(struct localnode *lcn, void *address)
{
	int i;

	if (lcn->family == IPV4) {
		log_debug("locallink_decsapcnt %s %pI4", lcn->hostname, address);
	} else {
		log_debug("locallink_decsapcnt %s %pI6c", lcn->hostname, address);
	}

	node_for_each_link(i, lcn) {
		if (!addr_cmp(lcn->family, &lcn->lcls[i].address, address)) {
			lcn->lcls[i].sapcnt--;
			lcn->totalsapcnt--;
			return 0;
		}
	}
	return -1;
}
int locallink_incsapcnt(struct localnode *lcn, void *address)
{
	int i;
	node_for_each_link(i, lcn) {
		if (!addr_cmp(lcn->family, &lcn->lcls[i].address, address)) {
			lcn->lcls[i].sapcnt++;
			lcn->totalsapcnt++;
			return 0;
		}
	}
	return -1;
}
/**********************************API for localnode**************************************/
void  locallink_expire_now(struct locallink *lcl)
{
	if (timer_pending(&lcl->timer) && time_after(lcl->timer.expires, jiffies)) {
		mod_timer_pending(&lcl->timer, jiffies);
		log_debug("locallink_expire_now mod_timer_pending");
	} else {
		mod_timer(&lcl->timer, jiffies);
		log_debug("locallink_expire_now mod_timer");
	}
}

void  locallink_update_expire_now(struct locallink *lcl)
{
	if (timer_pending(&lcl->timer) && time_after(lcl->timer.expires, jiffies)) {
		mod_timer_pending(&lcl->timer, jiffies);
		log_debug("locallink_update_expire_now mod_timer_pending");
	} else {
		log_debug("locallink_update_expire_now mod_timer second don't mod");
	}
}

void  locallink_expire(unsigned long data)
{
	int family;
	struct locallink *lcl = NULL;
	//struct locallink *old_lcl=NULL;
	//old_lcl = (struct locallink*)data;
	lcl = (struct locallink *)kmalloc(SIZEOFLCL, GFP_ATOMIC);
	memset(lcl, 0, SIZEOFLCL);
	memcpy(lcl, (struct locallink *)data, SIZEOFLCL);
	log_debug("[locallink_expire]socs_neigh_remove address");
	family = socs_neigh_remove(&lcl->address);

	if (FAMILY_IS_VALID(family)) {
		saptb_vote_for_expire(saptb_get(family), &lcl->address);
		socs_neigh_send_localnode();
	} else {
		log_err(ERR_REMOVE_FAILED, "socs_neigh_remove failed");
	}

	//send_hello_expire(lcl);
	kfree(lcl);
	//kfree(old_lcl);
	log_debug("locallink_expire");
}

int  localnode_settimer(struct localnode *lcn, int is_localnode)
{
	int i;
	unsigned int tmDead;
	int err;
	//struct locallink *lcl=NULL;
	node_for_each_link(i, lcn) {
		setup_timer(&lcn->lcls[i].timer, locallink_expire,
					(unsigned long)(&lcn->lcls[i]));

		if (!is_localnode) {
			//lcl=(struct locallink *)kmalloc(SIZEOFLCL,GFP_ATOMIC);
			//memset(lcl,0,SIZEOFLCL);
			//memset(&lcn->lcls[i].timer,0,sizeof(struct timer_list));
			//memcpy(lcl,&lcn->lcls[i],SIZEOFLCL);
			//lcl->timer.expires = 0;
			//setup_timer(&lcn->lcls[i].timer,locallink_expire,(unsigned long)lcl);
			socs_config_get(ARGP_TMDEAD, &tmDead);

			if ((err = mod_timer(&lcn->lcls[i].timer,
								 jiffies + msecs_to_jiffies(tmDead)))) {
				log_err(ERR_TIMER, "%s  mod_timer failed,err=%d", __func__, err);
				return -1;
			}
		}
	}
	return 0;
}
int localnode_update(struct localnode *old ,
					 struct localnode *new) /*only update the sapcnt and timer*/
{
	int i, j;
	int err;
	int result = 0;
	unsigned int tmDead;
	node_for_each_link(i, old) {
		node_for_each_link(j, new) {
			if (!(addr_cmp(old->family, &old->lcls[i].address,
						   &new->lcls[j].address))) { /*find */
				if (del_timer(&old->lcls[i].timer)) {
					socs_config_get(ARGP_TMDEAD, &tmDead);
					err = mod_timer(&old->lcls[i].timer, jiffies + msecs_to_jiffies(tmDead));

					if (err) {
						log_err(ERR_TIMER, "%s  mod_timer failed, err=%d\n", __func__, err);
						return err;
					}
				}

				memcpy(old->lcls[i].name, new->lcls[j].name, IFNAMSIZ);
				old->totalsapcnt = old->totalsapcnt - old->lcls[i].sapcnt + new->lcls[j].sapcnt;
				old->lcls[i].sapcnt = new->lcls[j].sapcnt;
				break;
			}
		}

		if (j == new->lcl_num) { /*not find in new,so expire_now*/
			log_debug("localnode_update not find in new,so expire_now %d", j);
			locallink_update_expire_now(&old->lcls[i]);//modify by Leo.Huang 2015-03-02
		}
	}
	node_for_each_link(j, new) {
		node_for_each_link(i, old) {
			if (!(addr_cmp(old->family, &old->lcls[i].address,
						   &new->lcls[j].address))) { /*find */
				break;
			}
		}

		if (i == old->lcl_num) { /*not find in the old */
			err = locallink_add(new->lcls[j].name, &new->lcls[j].address,
								new->lcls[j].sapcnt, 0, old);

			if (err < 0) {
				log_err(ERR_ADD_FAILED, "%s failed,err=%d", __func__, err);
				return err;
			} else {
				result = 1;
			}

			old->lcl_num++;
		}
	}
	return result;
}
void localnode_show(struct localnode *lcn)
{
	int i = 0;

	if (lcn->family == IPV4) {
		log_debug("hostname:%s\tfamily:%s\ttotalsapcnt:%u\tlcl_num:%u",
				  lcn->hostname, "IPV4", lcn->totalsapcnt, lcn->lcl_num);
	} else {
		log_debug("hostname:%s\tfamily:%s\ttotalsapcnt:%u\tlcl_num:%u",
				  lcn->hostname, "IPV6", lcn->totalsapcnt, lcn->lcl_num);
	}

	log_debug("name\taddress\t\t\tsapcnt\texpires\ttotalrq");
	node_for_each_link(i, lcn) {
		if (lcn->family == IPV4) {
			log_debug("%s\t%pI4\t\t%u\t%lu\t%u",
					  lcn->lcls[i].name, &lcn->lcls[i].address.ipv4,
					  lcn->lcls[i].sapcnt, lcn->lcls[i].timer.expires, atomic_read(&lcn->lcls[i].totalrq));
		} else if (lcn->family == IPV6) {
			log_debug("%s\t%pI6c\t%u\t%lu\t%u",
					  lcn->lcls[i].name, &lcn->lcls[i].address.ipv6,
					  lcn->lcls[i].sapcnt, lcn->lcls[i].timer.expires, atomic_read(&lcn->lcls[i].totalrq));
		}
	}
}

int localnode_get(struct localnode *lcn,
				  int family) /*only use in socs_neighX and get localnode info */
{
	struct net_device *dev = NULL;
	char hostname[HOSTNAMESIZE] = {};

	if (!lcn) {
		log_err(ERR_NULL_PTR, "%s lcn is NULL", __func__);
		return -1;
	}

	socs_config_get(ARGP_HOSTNAME, hostname);
	memcpy(lcn->hostname, hostname, HOSTNAMESIZE);
	lcn->family = family;
	lcn->totalsapcnt = 0;
	lcn->lcl_num = 0;
	for_each_netdev(&init_net, dev) {
		if (family == IPV4) {
			localnode4_get(dev, lcn);
		} else {
			localnode6_get(dev, lcn);
		}
	}
	return 0;
}
